package com.gitee.chili.leisure.util.captcha;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Objects;

@Data
@Slf4j
public class GeetestLib {

    protected final String verName = "4.0";
    protected final String sdkLang = "java";

    protected final String apiUrl = "http://api.geetest.com";

    protected final String registerUrl = "/register.php";
    protected final String validateUrl = "/validate.php";

    protected final String jsonFormat = "1";

    /**
     * 极验验证二次验证表单数据 challenge
     */
    public static final String challenge = "challenge";

    /**
     * 极验验证二次验证表单数据 validate
     */
    public static final String validate = "validate";

    /**
     * 极验验证二次验证表单数据 secCode
     */
    public static final String secCode = "secCode";

    /**
     * 公钥
     */
    private String captchaId = "";

    /**
     * 私钥
     */
    private String privateKey = "";

    /**
     * 是否开启新的failBack
     */
    private boolean newFailBack = false;

    /**
     * 返回字符串
     */
    private String responseStr = "";

    /**
     * 极验验证API服务状态Session Key
     */
    public String gtServerStatusSessionKey = "gt_server_status";

    public GeetestLib(String captchaId, String privateKey, boolean newFailBack) {
        this.captchaId = captchaId;
        this.privateKey = privateKey;
        this.newFailBack = newFailBack;
    }

    /**
     * 获取本次验证初始化返回字符串
     *
     * @return 初始化结果
     */
    public String getResponseStr() {
        return responseStr;
    }

    public String getVersionInfo() {
        return verName;
    }

    /**
     * 预处理失败后的返回格式串
     */
    private String getFailPreProcessRes() {

        Long rnd1 = Math.round(Math.random() * 100);
        Long rnd2 = Math.round(Math.random() * 100);
        String md5Str1 = md5Encode(rnd1 + "");
        String md5Str2 = md5Encode(rnd2 + "");
        String challenge = md5Str1 + md5Str2.substring(0, 2);

        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("success", 0);
            jsonObject.put("gt", this.captchaId);
            jsonObject.put("challenge", challenge);
            jsonObject.put("new_captcha", this.newFailBack);
        } catch (JSONException e) {
            printCaptchaLog("json dumps error");
        }
        return jsonObject.toString();
    }

    /**
     * 预处理成功后的标准串
     */
    private String getSuccessPreProcessRes(String challenge) {

        printCaptchaLog("challenge:" + challenge);
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("success", 1);
            jsonObject.put("gt", this.captchaId);
            jsonObject.put("challenge", challenge);
        } catch (JSONException e) {
            printCaptchaLog("json dumps error");
        }
        return jsonObject.toString();

    }

    /**
     * 验证初始化预处理
     *
     * @return 1表示初始化成功，0表示初始化失败
     */
    public int preProcess(HashMap<String, String> data) {

        if (registerChallenge(data) != 1) {
            this.responseStr = this.getFailPreProcessRes();
            return 0;
        }
        return 1;
    }

    /**
     * 用captchaID进行注册，更新challenge
     *
     * @return 1表示注册成功，0表示注册失败
     */
    private int registerChallenge(HashMap<String, String> data) {

        try {
            String userId = data.get("user_id");
            String clientType = data.get("client_type");
            String ipAddress = data.get("ip_address");

            String getUrl = apiUrl + registerUrl + "?";
            String param = "gt=" + this.captchaId + "&json_format=" + this.jsonFormat;

            if (userId != null) {
                param = param + "&user_id=" + userId;
            }
            if (clientType != null) {
                param = param + "&client_type=" + clientType;
            }
            if (ipAddress != null) {
                param = param + "&ip_address=" + ipAddress;
            }

            printCaptchaLog("GET_URL:" + getUrl + param);
            String result_str = readContentFromGet(getUrl + param);
            if (result_str.equals("fail")) {
                printCaptchaLog("gtServer register challenge failed");
                return 0;
            }

            printCaptchaLog("result:" + result_str);
            org.json.JSONObject jsonObject = new JSONObject(result_str);
            String return_challenge = jsonObject.getString("challenge");
            printCaptchaLog("return_challenge:" + return_challenge);

            if (return_challenge.length() == 32) {
                this.responseStr = this.getSuccessPreProcessRes(this.md5Encode(return_challenge + this.privateKey));
                return 1;
            } else {
                printCaptchaLog("gtServer register challenge error");
                return 0;
            }
        } catch (Exception e) {
            printCaptchaLog(e.toString());
            printCaptchaLog("exception:register api");
        }
        return 0;
    }

    /**
     * 判断一个表单对象值是否为空
     */
    protected boolean isEmpty(Object gtObj) {
        return Objects.isNull(gtObj) || StringUtils.isEmpty(gtObj.toString());
    }

    /**
     * 检查客户端的请求是否合法,三个只要有一个为空，则判断不合法
     */
    private boolean isLegitimate(String challenge, String validate, String secCode) {

        return isEmpty(challenge) || isEmpty(validate) || isEmpty(secCode);
    }

    /**
     * 服务正常的情况下使用的验证方式,向gt-server进行二次验证,获取验证结果
     *
     * @return 验证结果, 1表示验证成功0表示验证失败
     */
    public int validateRequest(String challenge, String validate, String secCode, HashMap<String, String> data) {

        if (isLegitimate(challenge, validate, secCode)) {
            return 0;
        }

        printCaptchaLog("request legitimate");

        String userId = data.get("user_id");
        String clientType = data.get("client_type");
        String ipAddress = data.get("ip_address");

        String postUrl = this.apiUrl + this.validateUrl;
        String param = String.format("challenge=%s&validate=%s&seccode=%s&json_format=%s", challenge, validate, secCode, this.jsonFormat);

        if (userId != null) {
            param = param + "&user_id=" + userId;
        }
        if (clientType != null) {
            param = param + "&client_type=" + clientType;
        }
        if (ipAddress != null) {
            param = param + "&ip_address=" + ipAddress;
        }

        printCaptchaLog("param:" + param);

        String response = "";
        try {
            if (validate.length() <= 0) {
                return 0;
            }
            if (!checkResultByPrivate(challenge, validate)) {
                return 0;
            }

            printCaptchaLog("checkResultByPrivate");
            response = readContentFromPost(postUrl, param);
            printCaptchaLog("response: " + response);
        } catch (Exception e) {
            e.printStackTrace();
        }

        String currentSecCode = "";

        try {

            JSONObject result = new JSONObject(response);
            currentSecCode = result.getString("seccode");
            printCaptchaLog("md5: " + md5Encode(currentSecCode));

            if (currentSecCode.equals(md5Encode(secCode))) {
                return 1;
            } else {
                return 0;
            }

        } catch (JSONException e) {
            printCaptchaLog("json load error");
            return 0;
        }

    }

    /**
     * failBack使用的验证方式
     *
     * @return 验证结果, 1表示验证成功0表示验证失败
     */
    public int validateRequest(String challenge, String validate, String secCode) {

        printCaptchaLog("in failBack validate");

        if (isLegitimate(challenge, validate, secCode)) {
            return 0;
        }
        printCaptchaLog("request legitimate");
        return 1;
    }

    /**
     * 输出debug信息，需要开启debugCode
     */
    public void printCaptchaLog(String message) {
        if (log.isDebugEnabled()) {
            log.debug("gee test logger info: " + message);
        }
    }

    protected boolean checkResultByPrivate(String challenge, String validate) {
        String encodeStr = md5Encode(privateKey + "geetest" + challenge);
        return validate.equals(encodeStr);
    }

    /**
     * 发送GET请求，获取服务器返回结果
     */
    private String readContentFromGet(String URL) throws IOException {

        URL getUrl = new URL(URL);
        HttpURLConnection connection = (HttpURLConnection) getUrl.openConnection();
        // 设置连接主机超时（单位：毫秒）
        connection.setConnectTimeout(2000);
        // 设置从主机读取数据超时（单位：毫秒）
        connection.setReadTimeout(2000);
        // 建立与服务器的连接，并未发送数据
        connection.connect();
        return getResult(connection);
    }

    private String getResult(HttpURLConnection connection) throws IOException {
        if (connection.getResponseCode() == 200) {
            // 发送数据到服务器并使用Reader读取返回的数据
            StringBuilder sb = new StringBuilder();
            InputStream inStream = null;
            byte[] buf = new byte[1024];
            inStream = connection.getInputStream();
            for (int n; (n = inStream.read(buf)) != -1; ) {
                sb.append(new String(buf, 0, n, StandardCharsets.UTF_8));
            }
            inStream.close();
            connection.disconnect();// 断开连接
            return sb.toString();
        } else {
            return "fail";
        }
    }

    /**
     * 发送POST请求，获取服务器返回结果
     */
    private String readContentFromPost(String URL, String data) throws IOException {

        printCaptchaLog(data);
        URL postUrl = new URL(URL);
        HttpURLConnection connection = (HttpURLConnection) postUrl.openConnection();

        connection.setConnectTimeout(2000);// 设置连接主机超时（单位：毫秒）
        connection.setReadTimeout(2000);// 设置从主机读取数据超时（单位：毫秒）
        connection.setRequestMethod("POST");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

        // 建立与服务器的连接，并未发送数据
        connection.connect();

        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8);
        outputStreamWriter.write(data);
        outputStreamWriter.flush();
        outputStreamWriter.close();

        return getResult(connection);
    }

    /**
     * md5 加密
     */
    private String md5Encode(String plainText) {
        String re_md5 = "";
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(plainText.getBytes());
            byte[] b = md.digest();
            int i;
            StringBuilder buf = new StringBuilder("");
            for (byte value : b) {
                i = value;
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }
            re_md5 = buf.toString();
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage(), e);
        }
        return re_md5;
    }

}
